package com.jvup.common.widget.snackbar;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityManager;

import androidx.annotation.IntRange;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;

import com.jvup.common.R;
import com.jvup.common.widget.ThemeEnforcement;
import com.jvup.common.widget.snackbar.SnackBarManager.Callback;
import com.jvup.common.widget.snackbar.SwipeDismissBehavior.OnDismissListener;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_DISMISS;

/**
 * 基础的瞬态条组件
 * @param <B> 具体实现的瞬态条类型
 */
public abstract class BaseTransientBar<B extends BaseTransientBar<B>> {
    public static final int LENGTH_INDEFINITE = -2;
    public static final int LENGTH_SHORT = -1;
    public static final int LENGTH_LONG = 0;
    static final int ANIMATION_DURATION = 250;
    static final int ANIMATION_FADE_DURATION = 180;
    static final Handler handler;
    static final int MSG_SHOW = 0;
    static final int MSG_DISMISS = 1;
    private final ViewGroup targetParent;
    private final Context context;
    protected final SnackBarLayout view;
    private int duration;
    private List<LifeStateChangeCallback<B>> callbacks;
    private Behavior behavior;
    private final AccessibilityManager accessibilityManager;
    final Callback managerCallback = new Callback() {
        public void show() {
            BaseTransientBar.handler.sendMessage(BaseTransientBar.handler.obtainMessage(MSG_SHOW, BaseTransientBar.this));
        }

        public void dismiss(int event) {
            BaseTransientBar.handler.sendMessage(BaseTransientBar.handler.obtainMessage(MSG_DISMISS, event, 0, BaseTransientBar.this));
        }
    };

    static {
        handler = new Handler(Looper.getMainLooper(), message -> {
            switch(message.what) {
                case MSG_SHOW:
                    ((BaseTransientBar)message.obj).showView();
                    return true;
                case MSG_DISMISS:
                    ((BaseTransientBar)message.obj).hideView(message.arg1);
                    return true;
                default:
                    return false;
            }
        });
    }

    protected BaseTransientBar(@NonNull ViewGroup parent, @NonNull View content) {
        if (parent == null) {
            throw new IllegalArgumentException("Transient bar must have non-null parent");
        } else if (content == null) {
            throw new IllegalArgumentException("Transient bar must have non-null content");
        } else {
            this.targetParent = parent;
            this.context = parent.getContext();
            ThemeEnforcement.checkAppCompatTheme(this.context);
            LayoutInflater inflater = LayoutInflater.from(this.context);
            this.view = (SnackBarLayout)inflater.inflate(this.getSnackBarBaseLayoutResId(), this.targetParent, false);
            this.view.addView(content);
            ViewCompat.setAccessibilityLiveRegion(this.view, ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
            ViewCompat.setImportantForAccessibility(this.view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
//            ViewCompat.setFitsSystemWindows(this.view, true);
            this.view.setFitsSystemWindows(true);
            ViewCompat.setOnApplyWindowInsetsListener(this.view, (v, insets) -> {
                v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), insets.getSystemWindowInsetBottom());
                return insets;
            });
            ViewCompat.setAccessibilityDelegate(this.view, new AccessibilityDelegateCompat() {
                public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
                    super.onInitializeAccessibilityNodeInfo(host, info);
                    info.addAction(ACTION_DISMISS);
                    info.setDismissable(true);
                }

                public boolean performAccessibilityAction(View host, int action, Bundle args) {
                    if (action == ACTION_DISMISS) {
                        BaseTransientBar.this.dismiss();
                        return true;
                    } else {
                        return super.performAccessibilityAction(host, action, args);
                    }
                }
            });
            this.accessibilityManager = (AccessibilityManager)this.context.getSystemService(Context.ACCESSIBILITY_SERVICE);
        }
    }

    @LayoutRes
    protected int getSnackBarBaseLayoutResId() {
        return R.layout.design_layout_snackbar;
    }

    @NonNull
    public B setDuration(int duration) {
        this.duration = duration;
        return (B) this;
    }

    public int getDuration() {
        return this.duration;
    }

    public B setBehavior(Behavior behavior) {
        this.behavior = behavior;
        return (B) this;
    }

    public Behavior getBehavior() {
        return this.behavior;
    }

    @NonNull
    public Context getContext() {
        return this.context;
    }

    @NonNull
    public View getView() {
        return this.view;
    }

    public void show() {
        SnackBarManager.getInstance().show(this.getDuration(), this.managerCallback);
    }

    public void dismiss() {
        this.dispatchDismiss(LifeStateChangeCallback.DISMISS_EVENT_MANUAL);
    }

    protected void dispatchDismiss(int event) {
        SnackBarManager.getInstance().dismiss(this.managerCallback, event);
    }

    @NonNull
    public B addCallback(@NonNull LifeStateChangeCallback<B> callback) {
        if (callback == null) {
            return (B) this;
        } else {
            if (this.callbacks == null) {
                this.callbacks = new ArrayList();
            }

            this.callbacks.add(callback);
            return (B) this;
        }
    }

    @NonNull
    public B removeCallback(@NonNull LifeStateChangeCallback<B> callback) {
        if (callback == null) {
            return (B) this;
        } else if (this.callbacks == null) {
            return (B) this;
        } else {
            this.callbacks.remove(callback);
            return (B) this;
        }
    }

    public boolean isShown() {
        return SnackBarManager.getInstance().isCurrent(this.managerCallback);
    }

    public boolean isShownOrQueued() {
        return SnackBarManager.getInstance().isCurrentOrNext(this.managerCallback);
    }

    protected SwipeDismissBehavior<? extends View> getNewBehavior() {
        return new Behavior();
    }

    abstract void animateViewIn();

    abstract void animateViewOut(final int event);

    final void showView() {
        if (this.view.getParent() == null) {
            ViewGroup.LayoutParams lp = this.view.getLayoutParams();
            if (lp instanceof CoordinatorLayout.LayoutParams) {
                CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams)lp;
                SwipeDismissBehavior<? extends View> behavior = this.behavior == null ? this.getNewBehavior() : this.behavior;
                if (behavior instanceof Behavior) {
                    ((Behavior)behavior).setBaseTransientBar(this);
                }

                behavior.setListener(new OnDismissListener() {
                    public void onDismiss(View view) {
                        view.setVisibility(View.GONE);
                        BaseTransientBar.this.dispatchDismiss(LifeStateChangeCallback.DISMISS_EVENT_SWIPE);
                    }

                    public void onDragStateChanged(int state) {
                        switch(state) {
                            case SwipeDismissBehavior.STATE_IDLE:
                                SnackBarManager.getInstance().restoreTimeoutIfPaused(BaseTransientBar.this.managerCallback);
                                break;
                            case SwipeDismissBehavior.STATE_DRAGGING:
                            case SwipeDismissBehavior.STATE_SETTLING:
                                SnackBarManager.getInstance().pauseTimeout(BaseTransientBar.this.managerCallback);
                        }

                    }
                });
                clp.setBehavior(behavior);
                clp.insetEdge = 80;
            }

            this.targetParent.addView(this.view);
        }

        this.view.setOnAttachStateChangeListener(new SnackBarLayout.OnAttachStateChangeListener() {
            public void onViewAttachedToWindow(View v) {
            }

            public void onViewDetachedFromWindow(View v) {
                if (isShownOrQueued()) {
                    handler.post(() -> BaseTransientBar.this.onViewHidden(LifeStateChangeCallback.DISMISS_EVENT_MANUAL));
                }

            }
        });
        if (ViewCompat.isLaidOut(this.view)) {
            if (this.shouldAnimate()) {
                this.animateViewIn();
            } else {
                this.onViewShown();
            }
        } else {
            this.view.setOnLayoutChangeListener((view, left, top, right, bottom) -> {
                BaseTransientBar.this.view.setOnLayoutChangeListener(null);
                if (BaseTransientBar.this.shouldAnimate()) {
                    BaseTransientBar.this.animateViewIn();
                } else {
                    BaseTransientBar.this.onViewShown();
                }

            });
        }

    }

    final void hideView(int event) {
        if (this.shouldAnimate() && this.view.getVisibility() == View.VISIBLE) {
            this.animateViewOut(event);
        } else {
            this.onViewHidden(event);
        }

    }

    void onViewShown() {
        SnackBarManager.getInstance().onShown(this.managerCallback);
        if (this.callbacks != null) {
            int callbackCount = this.callbacks.size();

            for(int i = callbackCount - 1; i >= 0; --i) {
                ((LifeStateChangeCallback)this.callbacks.get(i)).onShown(this);
            }
        }

    }

    void onViewHidden(int event) {
        SnackBarManager.getInstance().onDismissed(this.managerCallback);
        if (this.callbacks != null) {
            int callbackCount = this.callbacks.size();

            for(int i = callbackCount - 1; i >= 0; --i) {
                ((LifeStateChangeCallback)this.callbacks.get(i)).onDismissed(this, event);
            }
        }

        ViewParent parent = this.view.getParent();
        if (parent instanceof ViewGroup) {
            ((ViewGroup)parent).removeView(this.view);
        }

    }

    boolean shouldAnimate() {
        int feedbackFlags = 1;
        List<AccessibilityServiceInfo> serviceList = this.accessibilityManager.getEnabledAccessibilityServiceList(feedbackFlags);
        return serviceList != null && serviceList.isEmpty();
    }

    @RestrictTo({Scope.LIBRARY_GROUP})
    public static class BehaviorDelegate {
        private Callback managerCallback;

        public BehaviorDelegate(SwipeDismissBehavior<?> behavior) {
            behavior.setStartAlphaSwipeDistance(0.1F);
            behavior.setEndAlphaSwipeDistance(0.6F);
            behavior.setSwipeDirection(0);
        }

        public void setBaseTransientBar(BaseTransientBar<?> baseTransientBar) {
            this.managerCallback = baseTransientBar.managerCallback;
        }

        public boolean canSwipeDismissView(View child) {
            return child instanceof SnackBarLayout;
        }

        public void onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) {
            switch(event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    if (parent.isPointInChildBounds(child, (int)event.getX(), (int)event.getY())) {
                        SnackBarManager.getInstance().pauseTimeout(this.managerCallback);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    SnackBarManager.getInstance().restoreTimeoutIfPaused(this.managerCallback);
                case MotionEvent.ACTION_MOVE:
            }

        }
    }

    public static class Behavior extends SwipeDismissBehavior<View> {
        private final BehaviorDelegate delegate = new BehaviorDelegate(this);

        public Behavior() {
        }

        private void setBaseTransientBar(BaseTransientBar<?> baseTransientBar) {
            this.delegate.setBaseTransientBar(baseTransientBar);
        }

        public boolean canSwipeDismissView(View child) {
            return this.delegate.canSwipeDismissView(child);
        }

        public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) {
            this.delegate.onInterceptTouchEvent(parent, child, event);
            return super.onInterceptTouchEvent(parent, child, event);
        }
    }

    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo({Scope.LIBRARY_GROUP})
    @IntRange(from = 1L)
    public @interface Duration {
    }

    /**
     * 生命状态修改时回掉接口
     * @param <B> 发生更改的{@link BaseTransientBar}
     */
    public abstract static class LifeStateChangeCallback<B> {
        /**
         * 通过滑动被关闭
         */
        public static final int DISMISS_EVENT_SWIPE = 0;
        /**
         * 通过单击被关闭
         */
        public static final int DISMISS_EVENT_ACTION = 1;
        /**
         * 通过超时被关闭
         */
        public static final int DISMISS_EVENT_TIMEOUT = 2;
        /**
         * 通过调用 {@link #dismiss()} 被关闭.
         */
        public static final int DISMISS_EVENT_MANUAL = 3;
        /**
         * 通过新开的bar被关闭
         */
        public static final int DISMISS_EVENT_CONSECUTIVE = 4;

        public LifeStateChangeCallback() {
        }

        public void onDismissed(B transientBar, int event) {
        }

        public void onShown(B transientBar) {
        }

        @Retention(RetentionPolicy.SOURCE)
        @RestrictTo({Scope.LIBRARY_GROUP})
        public @interface DismissEvent {
        }
    }

}